﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Linq;
using PSE.Deployment.ConfigurationManager.Engine.Logging;
using PSE.Deployment.SetupBase.Configuration;
using PSE.Deployment.SetupBase.Configuration.Actions;
using PSE.Deployment.SetupBase.Configuration.Enums;
using PSE.Deployment.SetupBase.Parameter;
using System.Threading;

namespace PSE.Deployment.ConfigurationManager.Engine.Operations
{
    /// <summary>
    /// Classe de operacoes de Xml, herda de BaseOperations
    /// </summary>
    public class XmlOperations : BaseOperations
    {
        private List<string> _pseSubSystems;

        public XmlOperations(SetupParameter parameter)
            : base(parameter)
        {
            _pseSubSystems = new List<string>();
            _pseSubSystems.Add("ProcessManager");
            _pseSubSystems.Add("ServicesHoster");
            _pseSubSystems.Add("LoggingService");
            _pseSubSystems.Add("Fix");
        }

        /// <summary>
        /// Metodo que salva valores em um arquivo Xml
        /// </summary>
        /// <param name="xmlFile">Dados do arquivo Xml</param>
        /// <param name="config">Nome do arquivo xml</param>
        public bool SaveFile(XmlFile xmlFile, string config, string systemId)
        {
            return SaveFile(xmlFile, config, systemId, false);
        }

        /// <summary>
        /// Metodo que salva valores em um arquivo Xml
        /// </summary>
        /// <param name="xmlFile">Dados do arquivo Xml</param>
        /// <param name="config">Nome do arquivo xml</param>
        /// <param name="overwrite">Sobrescreve o xml</param>
        public bool SaveFile(XmlFile xmlFile, string config, string systemId, bool overwrite)
        {
            foreach (string ignoreFile in xmlFile.Folder.IgnoreFiles)
            {
                if (config.Contains(ignoreFile))
                    return false;
            }

            bool fileUpated = false;
            XmlDocument document = new XmlDocument();
            document.Load(config);

            Dictionary<string, List<XmlTag>> attributesToBeAdded = new Dictionary<string, List<XmlTag>>();

            foreach (XmlTag xmlTag in xmlFile.Tags)
            {
                if (xmlTag.OperationType == XmlTagOperationType.Insert)
                {
                    foreach (XmlXPath xmlPath in xmlTag.XPathList)
                    {
                        string xPathKey = xmlPath.XPath + "/" + xmlTag.Name;

                        if (attributesToBeAdded.ContainsKey(xPathKey))
                            attributesToBeAdded[xPathKey].Add(xmlTag);
                        else
                            attributesToBeAdded.Add(xPathKey, new List<XmlTag>() { xmlTag });

                        xmlTag.HasAnyPropertyChanged = true;
                    }
                }
                else
                {
                    if (!xmlTag.HasChanges && !overwrite)
                        continue;

                    foreach (XmlXPath xmlPath in xmlTag.XPathList)
                    {
                        XmlNodeList nodes = document.SelectNodes(xmlPath.XPath);

                        foreach (XmlNode node in nodes)
                        {
                            if (xmlTag.Type == XmlTagType.Endpoint || xmlTag.Type == XmlTagType.EndpointPort)
                            {
                                this.SetUriNode(xmlTag, node, xmlPath, ref fileUpated);
                            }
                            //else if (xmlTag.Type == XmlTagType.ChangeRootPath)
                            //{
                            //    string installationPath = Path.Combine(Parameter.InstallationPath, GetSystemId(systemId)) + Path.DirectorySeparatorChar;

                            //    node.Value = node.Value.Replace(@"\\", @"\");

                            //    int index = node.Value.IndexOf(xmlTag.Value, StringComparison.InvariantCultureIgnoreCase);

                            //    if (index != -1)
                            //    {
                            //        node.Value = node.Value.Remove(index, xmlTag.Value.Length);

                            //        node.Value = node.Value.Insert(index, installationPath);
                            //    }
                            //}
                            else if (xmlTag.Type == XmlTagType.CombineRootPath)
                            {
                                string installationPath = Path.Combine(Parameter.InstallationPath, GetSystemId(systemId)) + Path.DirectorySeparatorChar;

                                node.Value = Path.Combine(installationPath, xmlTag.Value);

                                fileUpated = true;
                            }
                            else
                            {
                                node.Value = xmlTag.Value;
                                fileUpated = true;
                            }
                        }
                    }
                }
            }

            foreach (KeyValuePair<string, List<XmlTag>> item in attributesToBeAdded)
            {
                XmlNodeList nodes = document.SelectNodes(item.Key);

                foreach (XmlNode node in nodes)
                {
                    node.ParentNode.RemoveChild(node);
                    fileUpated = true;
                }

                List<XmlTag> tags = attributesToBeAdded[item.Key];

                foreach (XmlTag tag in tags)
                {
                    bool hasAtLeastOneValue = false;

                    XmlElement element = document.CreateElement(tag.Name);

                    foreach (PSE.Deployment.SetupBase.Configuration.XmlAttribute at in tag.Attributes)
                    {
                        if (!at.HasChanges && !overwrite)
                            continue;

                        if (!hasAtLeastOneValue)
                            hasAtLeastOneValue = !String.IsNullOrEmpty(at.Value);

                        System.Xml.XmlAttribute attribute = document.CreateAttribute(at.Name);
                        attribute.Value = at.Value;
                        element.Attributes.Append(attribute);
                    }

                    if (hasAtLeastOneValue)
                    {
                        foreach (XmlXPath xmlPath in tag.XPathList)
                        {
                            document.SelectSingleNode(xmlPath.XPath).AppendChild(element);
                            fileUpated = true;
                        }
                    }
                }
            }

            if (fileUpated)
            {
                document.Save(config);
            }

            return fileUpated;
        }

        /// <summary>
        /// Metodo que salva valores de Uri em um arquivo Xml
        /// </summary>
        /// <param name="xmlTag">Tag do Xml a ser salva</param>
        /// <param name="node">Nó do Xml a ser salvo</param>
        void SetUriNode(XmlTag xmlTag, XmlNode node, XmlXPath xPath, ref bool fileUpdated)
        {
            if (!String.IsNullOrEmpty(node.Value) && Uri.IsWellFormedUriString(node.Value, UriKind.Absolute))
            {
                UriBuilder uri = new UriBuilder(new Uri(node.Value));

                if (xmlTag.Type == XmlTagType.Endpoint)
                {
                    uri.Host = xmlTag.Value;
                }
                else if (xmlTag.Type == XmlTagType.EndpointPort)
                {
                    if (uri.Scheme == "net.tcp")
                    {
                        uri.Port = Convert.ToInt32(xmlTag.Value);
                    }
                }

                node.Value = uri.ToString();
                fileUpdated = true;
            }
            else
            {
                //Usará o valor do item selecionado da XmlTag ao invés do valor da própria xmlTag
                if (xPath.UseTagItemValue)
                {
                    if (xmlTag.ControlItemSelected != null)
                    {
                        node.Value = xmlTag.ControlItemSelected.TagItemValue;
                        fileUpdated = true;
                    }
                }
                else
                {
                    node.Value = xmlTag.Value;
                    fileUpdated = true;
                }
            }
        }

        /// <summary>
        /// Metodo responsavel por atualizar dados de um arquivo Xml
        /// </summary>
        /// <param name="regKeys">Chaves a serem alteradas</param>
        public bool Save(List<UpdateConfig> updateConfigs, List<Group> allGroups, out List<string> affectedGroups)
        {
            bool isSaved = false;

            affectedGroups = new List<string>();

            foreach (UpdateConfig updateConfig in updateConfigs)
            {
                Group group = allGroups.Find(g => g.Id == updateConfig.GroupId);

                foreach (XmlFile xmlFile in updateConfig.XmlFiles)
                {
                    base.EncryptTags(xmlFile.Tags);

                    if (!xmlFile.HasAnyPropertyChanged)
                        continue;

                    string path = Path.Combine(Parameter.InstallationPath, xmlFile.Folder.Destination);

                    try
                    {
                        IEnumerable<string> files;

                        if (xmlFile.Folder.Files.Count == 0)
                            files = Directory.GetFiles(path, "*.config", SearchOption.AllDirectories);
                        else
                            files = PSE.Deployment.Util.File.GetFiles(path, xmlFile.Folder.Files);

                        foreach (string file in files)
                        {
                            string config;

                            if (!Path.IsPathRooted(file))
                                config = Path.Combine(path, file);
                            else
                                config = file;

                            try
                            {
                                bool fileUpated = false;

                                for (int i = 0; i < 3; i++)
                                {
                                    try
                                    {
                                        fileUpated = SaveFile(xmlFile, config, group.System.Id);
                                        break;
                                    }
                                    catch (Exception exTry)
                                    {
                                        Thread.Sleep(3000);
                                        OperationLog.Write("Error while trying to save Xml configuration file (" + config + ")", exTry);
                                        continue;
                                    }
                                }

                                if (fileUpated)
                                {
                                    OperationLog.Write("Xml configuration file (" + config + ") saved successfully");
                                }
                            }
                            catch (Exception exSave)
                            {
                                OperationLog.Write("Error while trying to save Xml configuration file (" + config + ")", exSave);
                            }
                        }

                        if (!affectedGroups.Contains(updateConfig.GroupId))
                            affectedGroups.Add(updateConfig.GroupId);

                        xmlFile.HasAnyPropertyChanged = false;

                        isSaved = true;

                        OperationLog.Write("Xml configuration file (" + xmlFile.Description + ") saved successfully");
                    }
                    catch (Exception ex)
                    {
                        OperationLog.Write("Error while trying to save Xml configuration file (" + xmlFile.Description + ")", ex);
                    }
                }
            }

            return isSaved;
        }

        private string GetSystemId(string systemId)
        {
            if (_pseSubSystems.Any(s => s.Equals(systemId, StringComparison.CurrentCultureIgnoreCase)))
                return "PSE";
            else
                return systemId;
        }
    }
}
